package handler

import (
	"errors"
	"fmt"
	"log"
	"moft/database"
	"moft/model"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type TicketHandler struct {
	db *gorm.DB
}

func NewTicketHandler() *TicketHandler {
	return &TicketHandler{
		db: database.DB(),
	}
}

type createTicketRequestMessage struct {
	Title    string `json:"title" binding:"required"`
	Message  string `json:"message" binding:"required"`
	Priority string `json:"priority" binding:"required"`
	Type     string `json:"type" binding:"required"`
	ClientID string `json:"client_id" binding:"required"`
}

func (body *createTicketRequestMessage) ToTicket() *model.Ticket {
	return &model.Ticket{
		Title:    body.Title,
		Message:  body.Message,
		Priority: model.TicketPriority(body.Priority),
		Type:     body.Type,
		ClientID: body.ClientID,
	}
}

func (body *createTicketRequestMessage) parseMultipartFormValues(values map[string][]string) (status int, err error) {
	title, ok := values["title"]
	if !ok {
		return http.StatusNotFound, errors.New("missing title")
	} else if len(title) != 1 {
		return http.StatusBadRequest, errors.New("the title values error, only the length limit is one")
	}

	message, ok := values["message"]
	if !ok {
		return http.StatusBadRequest, errors.New("missing message")
	} else if len(message) != 1 {
		return http.StatusBadRequest, errors.New("the message error, only the length limit is one")
	}

	priority, ok := values["priority"]
	if !ok {
		return http.StatusNotFound, errors.New("missing priority")
	} else if len(priority) != 1 {
		return http.StatusBadRequest, errors.New("the priority error, only the length limit is one")
	}

	Type, ok := values["Type"]
	if !ok {
		return http.StatusNotFound, errors.New("missing type")
	} else if len(Type) != 1 {
		return http.StatusBadRequest, errors.New("the type error, only the length limit is one")
	}

	clientID, ok := values["client_id"]
	if !ok {
		return http.StatusNotFound, errors.New("missing client id")
	} else if len(clientID) != 1 {
		return http.StatusBadRequest, errors.New("the client id error, only the length limit is one")
	}

	body.Message = message[0]
	body.Title = title[0]
	body.Priority = priority[0]
	body.Type = Type[0]
	body.ClientID = clientID[0]
	return 200, nil
}

func (h *TicketHandler) CreateTicket(c *gin.Context) {
	// parse forms.
	form, _ := c.MultipartForm()
	requestBody := &createTicketRequestMessage{}
	requestBody.parseMultipartFormValues(form.Value)

	_, ok := model.IsSpecifiedPriority(requestBody.Priority)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "the ticket priority name error",
		})
		return
	}
	_, err := model.FindRoleByName(h.db, requestBody.Type)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.JSON(http.StatusNotFound, gin.H{
				"error": fmt.Errorf("the ticket type %s does not exist", requestBody.Type),
			})
			return
		}
		log.Printf("find role by name %s failed: %v", requestBody.Type, err)
		c.Status(http.StatusInternalServerError)
		return
	}
	_, err = model.FindClient(h.db, requestBody.ClientID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.JSON(http.StatusNotFound, gin.H{
				"error": fmt.Errorf("the client %s does not exist", requestBody.ClientID),
			})
			return
		}
		log.Printf("find client(%s) failed: %v", requestBody.ClientID, err)
		c.Status(http.StatusInternalServerError)
		return
	}

	ticket := requestBody.ToTicket()

	// append user id.
	ticket.UserID = c.GetString("user_id")

	// parse appendix, eg: photo, file.
	values := url.Values{}
	files := form.File["files"]
	for i, file := range files {
		rawName := file.Filename + strconv.FormatInt(int64(i), 10)
		name := h.parseFileName(rawName)
		values.Add("files", name)

		if err := c.SaveUploadedFile(file, name); err != nil {
			log.Printf("upload file failed: %v", err)
			c.Status(http.StatusInternalServerError)
			return
		}
	}
	ticket.AppendixPath = values.Encode()

	// create ticket.
	if err := model.CreateTicket(h.db, ticket); err != nil {
		log.Printf("create ticket failed: %v", err)
		c.Status(http.StatusInternalServerError)
		return
	}

	c.JSON(http.StatusCreated, gin.H{
		"ticket": ticket,
	})
}

func (h *TicketHandler) parseFileName(rawName string) string {
	ext := filepath.Ext(rawName)
	name := strings.TrimSuffix(rawName, ext)
	newName := name + strconv.FormatInt(time.Now().Unix(), 10) + ext
	return model.FilesPrefix + strings.TrimPrefix(newName, "/")
}

func (h *TicketHandler) Update(c *gin.Context) {
	var payload struct {
		Title    string `json:"title"`
		Message  string `json:"message"`
		Priority string `json:"priority"`
		Type     string `json:"type"`
		ClientID string `json:"client_id"`
	}
	if err := c.ShouldBindJSON(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err.Error(),
		})
		return
	}
	priority, ok := model.IsSpecifiedPriority(payload.Priority)
	if !ok {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": "the priority content error",
		})
		return
	}

	ticketID := c.Param("ticket_id")

	ticket := &model.Ticket{
		ID:       ticketID,
		Message:  payload.Message,
		Priority: priority,
		Type:     payload.Type,
		ClientID: payload.ClientID,
	}
	if err := model.UpdateTicket(h.db, ticket); err != nil {
		log.Printf("update ticket failed: %v", err)
		c.Status(http.StatusInternalServerError)
		return
	}

	t, err := model.FindTicket(h.db, ticketID)
	if err != nil {
		log.Printf("find ticket by id %s failed: %v", ticketID, err)
		c.Status(http.StatusInternalServerError)
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"ticket": t,
	})
}

func (h *TicketHandler) List(c *gin.Context) {
	var payload map[string]interface{}
	if err := c.ShouldBindQuery(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{
			"error": err.Error(),
		})
		return
	}

	tickets, err := model.ListTickets(h.db, payload)
	if err != nil {
		log.Printf("list tickets failed: %v", err)
		c.Status(http.StatusInternalServerError)
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"tickets": tickets,
	})
}

func (h *TicketHandler) Delete(c *gin.Context) {
	ticketID := c.Param("ticket_id")
	err := model.DeleteTicket(h.db, ticketID)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			c.JSON(http.StatusBadRequest, gin.H{
				"error": fmt.Sprintf("the ticket(%s) does not exist", ticketID),
			})
			return
		}
		log.Printf("delete ticket by id %s failed: %v", ticketID, err)
		c.Status(http.StatusInternalServerError)
		return
	}

	c.Status(http.StatusNoContent)
}
